package org.app.appjson.reader.charreader;

import org.app.appjson.exception.AppJsonException;
import org.app.appjson.reader.interfaces.CharReader;

import java.io.IOException;
import java.util.Objects;

/**
 * 字符读取器
 * @author lizhongsi
 * @version 1.0
 * @since 1.0
 */
public class StringReader implements CharReader {

    private String string;

    /**
     *  待读取字符的数组位置
     *  peek()会读取buffer[nextChar]的值，nextChar值不改变
     *  next()会读取buffer[nextChar]的值，nextChar++
     */
    private int nextChar;
    /* 已读取的字符在全部字符串中的位置 */
    private int index;

    public StringReader(String string) {
        Objects.requireNonNull(string, "string is null");
        index = -1;
        this.string = string;
    }

    @Override
    public boolean hasNext() {
        if (nextChar >= string.length()) {
            close();
            return false;
        }
        return true;
    }

    @Override
    public char peek() {
        if (!hasNext()) {
            throw new IllegalStateException("Document finished.");
        }
        return this.string.charAt(nextChar);
    }

    @Override
    public char next() throws IOException {
        char ch = peek();
        nextChar++;
        index++;
        return ch;
    }

    // 已读取的字符在全部字符串中的位置
    public int index() {
        return index;
    }


    @Override
    public void close() {
        this.string = null;
    }

    // 读取一个数字
    // 使用状态机实现，状态变换参考"json词法number解析状态图.jpg"
    @Override
    public String readNumber() throws AppJsonException {
        try {
            int start = nextChar, end = nextChar;
            byte state = 1;
            char ch;
            for (;;) {
                ch = this.peek();
                switch (state) {
                    case 1:
                        if (ch == '-') {
                            end++;
                            this.next();
                        }
                        state = 2;
                        break;
                    case 2:
                        if (ch == '0') {
                            end++;
                            this.next();
                            state = 3;
                        } else {
                            if (ch >= '1' && ch <= '9') {
                                end++;
                                this.next();
                                state = 4;
                            } else {
                                throw new AppJsonException("JsonLexer error! Expect a number but read char:" + ch + ",index:" + this.index());
                            }
                        }
                        break;
                    case 3:
                        if (ch == '.') {
                            end++;
                            this.next();
                            state = 5;
                        } else {
                            state = 6;
                        }
                        break;
                    case 4:
                        if (ch >= '0' && ch <= '9') {
                            end++;
                            this.next();
                            // state not change
                        } else {
                            state = 3;
                        }
                        break;
                    case 5:
                        if (ch >= '0' && ch <= '9') {
                            end++;
                            this.next();
                            // state not change
                        } else {
                            state = 6;
                        }
                        break;
                    case 6:
                        if (ch == 'e' || ch == 'E') {
                            end++;
                            this.next();
                            state = 7;
                        } else {
                            state = 10;
                        }
                        break;
                    case 7:
                        if (ch == '+' || ch == '-') {
                            end++;
                            this.next();
                        }
                        state = 8;
                        break;
                    case 8:
                        if (ch >= '0' && ch <= '9') {
                            end++;
                            this.next();
                            state = 9;
                        } else {
                            throw new AppJsonException("JsonLexer error! Expect a number but read char:" + ch + ",index:" + this.index());
                        }
                        break;
                    case 9:
                        if (ch >= '0' && ch <= '9') {
                            end++;
                            this.next();
                            // state not change
                        } else {
                            state = 10;
                        }
                        break;
                    case 10:
                        return string.substring(start, end);
                    default:
                        throw new AppJsonException("JsonLexer error! The char:" + ch + ",index:" + this.index());
                }
            }
        } catch (IOException e) {
            close();
        }
        return null;
    }

    /**
     * 读取一个字符串
     * @return 字符串
     * @throws AppJsonException 异常
     */
    @Override
    public String readString() throws AppJsonException {
        try {
            this.next();// 跳过引号
            int start =nextChar, end = nextChar;
            char ch;
            for (;;) {
                ch = this.peek();
                if (ch == '\\') {
                    this.next();
                    // 转义字符 escape: \" \\ \/ \b \f \n \r \t
                    char ech = this.next();
                    switch (ech) {
                        case '\"':
                        case 'n':
                        case '\\':
                        case '/':
                        case 'b':
                        case 'f':
                        case 'r':
                        case 't':
                            end++;
                            break;
                        case 'u':
                            // read an unicode uXXXX:
                            int u = 0;
                            for (int i = 0; i < 4; i++) {
                                char uch = this.next();
                                if (uch >= '0' && uch <= '9') {
                                    u = (u << 4) + (uch - '0');
                                } else if (uch >= 'a' && uch <= 'f') {
                                    u = (u << 4) + (uch - 'a') + 10;
                                } else if (uch >= 'A' && uch <= 'F') {
                                    u = (u << 4) + (uch - 'A') + 10;
                                } else {
                                    throw new AppJsonException("JsonLexer error! Unexpected char:" + uch + ",index:" + index());
                                }
                            }
                            end++;
                            break;
                        default:
                            throw new AppJsonException("JsonLexer error! Unexpected char:" + ch + ",index:" + index());
                    }
                } else if (ch == '\"') {
                    // end of string.
                    this.next();
                    break;
                } else if (ch == '\r' || ch == '\n') {
                    throw new AppJsonException("JsonLexer error! Unexpected char:" + ch + ",index:" + index());
                } else {
                    this.next();
                    end++;
                }
            }
            return string.substring(start, end);
        } catch (IOException e) {
            close();
        }
        return null;
    }
}
